package com.jsy.mini12306.ticketsales.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jsy.mini12306.common.entity.SeatType;
import com.jsy.mini12306.common.entity.TrainStationSeatStatus;
import com.jsy.mini12306.ticketsales.bo.DrivingPlan;
import com.jsy.mini12306.ticketsales.entity.SeatLocation;
import com.jsy.mini12306.ticketsales.entity.TrainPatternSeatMap;
import com.jsy.mini12306.ticketsales.exception.TicketSaleCoreException;
import com.jsy.mini12306.ticketsales.mapper.TrainPatternSeatMapMapper;
import com.jsy.mini12306.ticketsales.mapper.TrainStationSeatStatusMapper;
import com.jsy.mini12306.ticketsales.service.RemainingSeatsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author: SongyangJi
 * @description:
 * @since: 2021/9/2
 */

@Slf4j
@Service("remainingSeatsService")
public class RemainingSeatsServiceImpl extends ServiceImpl<TrainStationSeatStatusMapper, TrainStationSeatStatus> implements RemainingSeatsService {

    private static final int MAX_OPTIMISTIC_LOCK_RETRIES = 2;

    @Resource
    TrainStationSeatStatusMapper trainStationSeatStatusMapper;

    @Resource
    TrainPatternSeatMapMapper seatMapper;


    /**
     * 位图容量检查
     *
     * @param trainStationSeatStatuses
     * @throws TicketSaleCoreException
     */
    private void checkHasSameCapacity(List<TrainStationSeatStatus> trainStationSeatStatuses) throws TicketSaleCoreException {
        if (trainStationSeatStatuses.isEmpty()) {
            throw new TicketSaleCoreException("这站次当日的座位位图为空");
        }
        Integer len = null;
        for (TrainStationSeatStatus trainStationSeatStatus : trainStationSeatStatuses) {
            if (len == null) {
                len = trainStationSeatStatus.getStatus().length();
            }
            if (len != trainStationSeatStatus.getStatus().length()) {
                throw new TicketSaleCoreException("列车的座位容量在各个站点不同");
            }
        }

    }

    /**
     * 二进制串的按位或运算
     *
     * @param trainStationSeatStatuses
     * @return
     */
    private boolean[] getAvailSeats(List<TrainStationSeatStatus> trainStationSeatStatuses) {
        int len = trainStationSeatStatuses.get(0).getStatus().length();
        boolean[] occupy = new boolean[len];
        trainStationSeatStatuses.forEach(trainStationSeatStatus -> {
            char[] chars = trainStationSeatStatus.getStatus().toCharArray();
            for (int i = 0; i < chars.length; i++) {
                char c = chars[i];
                if (c == '1') {
                    occupy[i] = true;
                } else if (c != '0') {
                    // TODO 异常处理
                    log.error("database consistency error");
//                    try {
//                        throw new TicketSaleCoreException("列车的座位占用位图不为 0 也不为 1 ");
//                    } catch (TicketSaleCoreException e) {
//                        e.printStackTrace();
//                    }
                }
            }
        });
        return occupy;
    }

    /**
     * 可用位图索引
     *
     * @param occupy
     * @return
     */
    private List<Integer> getAvailSeats(boolean[] occupy) {
        ArrayList<Integer> indexes = new ArrayList<>();
        for (int i = 0; i < occupy.length; i++) {
            if (!occupy[i]) {
                indexes.add(i);
            }
        }
        return indexes;
    }

    /**
     * 随机抽取可用座位
     *
     * @param occupy
     * @return
     */
    private int randomSeatIndex(boolean[] occupy) {
        Random rand = new Random();
        Integer seatIndex = getAvailSeats(occupy).get(rand.nextInt(occupy.length));
        occupy[seatIndex] = true;
        return seatIndex;
    }

    private void occupyOneSeat(int seatIndex, List<TrainStationSeatStatus> trainStationSeatStatuses) {
        trainStationSeatStatuses.forEach(trainStationSeatStatus -> {
            char[] chars = trainStationSeatStatus.getStatus().toCharArray();
            chars[seatIndex] = '1';
            trainStationSeatStatus.setStatus(new String(chars));
        });
    }


    @Override
    public int checkAvailSeatsOfDrivingPlan(DrivingPlan drivingPlan, SeatType seatType) {
        List<TrainStationSeatStatus> ids = drivingPlan.getSeatStatusOfPassStations(seatType);
        if (ids.isEmpty()) {
            return 0;
        }

        int trainId = ids.get(0).getTrainId();

        // 需要的站点的座次位图
        List<Integer> stations = new ArrayList<>();
        ids.forEach(trainStationSeatStatus -> {
            stations.add(trainStationSeatStatus.getStationId());
        });

        // 从 db 中查出 位图status （这里是无锁的读）
        List<TrainStationSeatStatus> trainStationSeatStatuses = trainStationSeatStatusMapper.selectList(new QueryWrapper<TrainStationSeatStatus>()
                .eq("train_id", trainId)
                .eq("type", seatType)
                .in("station_id", stations));

        if (trainStationSeatStatuses.isEmpty()) {
            return 0;
        }

        // 检查
        try {
            checkHasSameCapacity(trainStationSeatStatuses);
        } catch (TicketSaleCoreException e) {
            // TODO
//                e.printStackTrace();
        }
        // 二进制串作按位或操作
        boolean[] availSeats = getAvailSeats(trainStationSeatStatuses);
        return getAvailSeats(availSeats).size();
    }

    @Override
    public boolean hasAvailSeats(DrivingPlan drivingPlan, SeatType seatType) {
        return checkAvailSeatsOfDrivingPlan(drivingPlan, seatType) > 0;
    }

    @Override
    public Map<SeatType, Integer> checkAvailSeatsOfDrivingPlan(DrivingPlan drivingPlan) {
        Map<SeatType, Integer> typesSeatNum = new HashMap<>();
        for (SeatType seatType : SeatType.values()) {
            // 获得当前座位类型的容量
            typesSeatNum.put(seatType, checkAvailSeatsOfDrivingPlan(drivingPlan, seatType));
        }
        return typesSeatNum;
    }


    @Override
    public boolean hasAvailSeats(DrivingPlan drivingPlan) {
        Map<SeatType, Integer> seatTypeIntegerMap = checkAvailSeatsOfDrivingPlan(drivingPlan);
        for (Integer value : seatTypeIntegerMap.values()) {
            if (value > 0) return true;
        }
        return false;
    }


    @Transactional
    @Override
    public SeatLocation buyTicketAndOccupySeatWithXLock(DrivingPlan drivingPlan, SeatType seatType) throws TicketSaleCoreException {
//        System.out.println("seatType: "+ seatType);
        Integer trainId = drivingPlan.getTrain().getTrainId();
        LocalDate workingDate = drivingPlan.getWorkingDate();
        ArrayList<Integer> stationIds = new ArrayList<>();
        drivingPlan.getSeatStatusOfPassStations(seatType).forEach(trainStationSeatStatus -> stationIds.add(trainStationSeatStatus.getStationId()));
        // 加互斥锁，一次性拿到所有需要的状态位图
        List<TrainStationSeatStatus> trainStationSeatStatuses = trainStationSeatStatusMapper.selectWithXLock(trainId, workingDate, seatType, stationIds);
        // 检查位图结构
        checkHasSameCapacity(trainStationSeatStatuses);
        // 二进制串集作按位或运算
        boolean[] availSeats = getAvailSeats(trainStationSeatStatuses);
        // 获取所有可用的座位
        List<Integer> availSeatsIds = getAvailSeats(availSeats);
        if (availSeatsIds.isEmpty()) {
            return null;
        }
        // 随机从空闲座位中挑一个空闲座位
        int idx = randomSeatIndex(availSeats);
        // 更改位图
        occupyOneSeat(idx, trainStationSeatStatuses);

        // 更改座位-位图状态
        trainStationSeatStatuses.forEach(trainStationSeatStatus -> {
            Integer stationId = trainStationSeatStatus.getStationId();
            trainStationSeatStatusMapper.update(
                    trainStationSeatStatus,
                    new UpdateWrapper<TrainStationSeatStatus>()
                            .eq("train_id", trainId)
                            .eq("working_date", workingDate)
                            .eq("type", seatType)
                            .eq("station_id", stationId)
            );

        });

        // 查询对应位置的座位
        System.out.println("返回位置座位: " + idx);
        System.out.println("train_pattern_id: "+drivingPlan.getTrain().getTrainPatternId());
        System.out.println("type: "+ seatType);
        return seatMapper.selectOne(new QueryWrapper<TrainPatternSeatMap>()
                .eq("train_pattern_id", drivingPlan.getTrain().getTrainPatternId())
                .eq("type", seatType)
                .eq("idx", idx)).getSeatLocation();
    }


    // TODO 尚未完成
    @Override
    public SeatLocation buyTicketAndOccupySeatWithSLock(DrivingPlan drivingPlan, SeatType seatType) throws TicketSaleCoreException {
        Integer trainId = drivingPlan.getTrain().getTrainId();
//         先乐观锁尝试
        for (int i = 0; i < MAX_OPTIMISTIC_LOCK_RETRIES; i++) {
            // 无锁读
            List<TrainStationSeatStatus> seatStatusOfPassStations = drivingPlan.getSeatStatusOfPassStations(seatType);
            checkHasSameCapacity(seatStatusOfPassStations);
            boolean[] availSeats = getAvailSeats(seatStatusOfPassStations);
            List<Integer> availSeatsIds = getAvailSeats(availSeats);
            // 快照读，若此时无票，则直接返回
            if (availSeatsIds.isEmpty()) {
                return null;
            }
            int idx = randomSeatIndex(availSeats);
            occupyOneSeat(idx, seatStatusOfPassStations);

            // 需要修改的行数
            int needToModify = seatStatusOfPassStations.size();
            // 成功修改的行数
            int countOfSuccessModification = 0;
            for (TrainStationSeatStatus trainStationSeatStatus : seatStatusOfPassStations) {
                int succ = trainStationSeatStatusMapper.update(trainStationSeatStatus,
                        new UpdateWrapper<TrainStationSeatStatus>()
                                .eq("train_id", trainId)
                                .eq("type", trainStationSeatStatus.getType())
                                .eq("station_id", trainStationSeatStatus.getStationId()));
                countOfSuccessModification += succ;
            }
            // 乐观锁争抢修改成功
            if (needToModify == countOfSuccessModification) {
                // 可以成功返回
                break;
            }
        }
        return null;
    }

    @Override
    public SeatLocation buyTicketAndOccupySeat(DrivingPlan drivingPlan, SeatType seatType) throws TicketSaleCoreException {
        return buyTicketAndOccupySeatWithXLock(drivingPlan, seatType);
    }
}
